﻿
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace Cyl.ImageControl;

public class CylImageControl : Control
{
    private VisualCollection _visuals;
    private DrawingVisual _imageVisual;
    private DrawingVisual _roiVisual;
    private DrawingVisual _controlPointsVisual;
    private DrawingVisual _textAnnotationsVisual;
    private ROI _currentRoi;
    private bool _isDrawing;
    private bool _isEditing;
    private bool _isMoving;
    private double _scale = 1.0;
    private const double ScaleStep = 0.1;
    private Point _offset = new Point(0, 0);
    private TextBlock _infoTextBlock;
    private string _currentRoiType = "Rectangle";
    private Point? _draggingControlPoint;
    private Point _lastMousePosition;

    public static readonly DependencyProperty ImageProperty =
        DependencyProperty.Register("Image", typeof(ImageSource), typeof(CylImageControl), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, OnImageChanged));

    public static readonly DependencyProperty IsROIFunctionalityEnabledProperty =
        DependencyProperty.Register("IsROIFunctionalityEnabled", typeof(bool), typeof(CylImageControl), new PropertyMetadata(true, OnIsROIFunctionalityEnabledChanged));

    public static readonly DependencyProperty ShowCrosshairProperty =
        DependencyProperty.Register("ShowCrosshair", typeof(bool), typeof(CylImageControl), new PropertyMetadata(false, OnShowCrosshairChanged));

    public static readonly DependencyProperty ROIsProperty =
        DependencyProperty.Register("ROIs", typeof(List<ROI>), typeof(CylImageControl), new PropertyMetadata(new List<ROI>(), OnROIsChanged));

    public static readonly DependencyProperty TextAnnotationsProperty =
        DependencyProperty.Register("TextAnnotations", typeof(List<TextAnnotation>), typeof(CylImageControl), new PropertyMetadata(new List<TextAnnotation>(), OnTextAnnotationsChanged));

    public ImageSource Image
    {
        get { return (ImageSource)GetValue(ImageProperty); }
        set { SetValue(ImageProperty, value); }
    }

    public bool IsROIFunctionalityEnabled
    {
        get { return (bool)GetValue(IsROIFunctionalityEnabledProperty); }
        set { SetValue(IsROIFunctionalityEnabledProperty, value); }
    }

    public bool ShowCrosshair
    {
        get { return (bool)GetValue(ShowCrosshairProperty); }
        set { SetValue(ShowCrosshairProperty, value); }
    }

    public List<ROI> ROIs
    {
        get { return (List<ROI>)GetValue(ROIsProperty); }
        set { SetValue(ROIsProperty, value); }
    }

    public List<TextAnnotation> TextAnnotations
    {
        get { return (List<TextAnnotation>)GetValue(TextAnnotationsProperty); }
        set { SetValue(TextAnnotationsProperty, value); }
    }

    public CylImageControl()
    {
        _visuals = new VisualCollection(this);
        _imageVisual = new DrawingVisual();
        _roiVisual = new DrawingVisual();
        _controlPointsVisual = new DrawingVisual();
        _textAnnotationsVisual = new DrawingVisual();
        _visuals.Add(_imageVisual);
        _visuals.Add(_roiVisual);
        _visuals.Add(_controlPointsVisual);
        _visuals.Add(_textAnnotationsVisual);

        _infoTextBlock = new TextBlock
        {
            Foreground = Brushes.Yellow,
            Background = Brushes.Transparent,
            Padding = new Thickness(2)
        };
        AddVisualChild(_infoTextBlock);

        this.Background = Brushes.Black;

        this.MouseLeftButtonDown += OnMouseLeftButtonDown;
        this.MouseMove += OnMouseMove;
        this.MouseLeftButtonUp += OnMouseLeftButtonUp;
        this.MouseWheel += OnMouseWheel;
        this.MouseRightButtonDown += OnMouseRightButtonDown;

        this.SizeChanged += OnSizeChanged;

        CreateContextMenu();
    }

    private static void OnImageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = (CylImageControl)d;
        control.FitToWindow();
        control.DrawImage();
    }

    private static void OnIsROIFunctionalityEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = (CylImageControl)d;
        control.UpdateContextMenu();
    }

    private static void OnShowCrosshairChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = (CylImageControl)d;
        control.DrawImage();
    }

    private static void OnROIsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = (CylImageControl)d;
        control.DrawROIs();
    }

    private static void OnTextAnnotationsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = (CylImageControl)d;
        control.DrawTextAnnotations();
    }

    private void OnSizeChanged(object sender, SizeChangedEventArgs e)
    {
        FitToWindow();
        DrawImage();
        DrawROIs();
        DrawTextAnnotations();
    }

    private void FitToWindow()
    {
        if (Image == null) return;

        double imageAspectRatio = Image.Width / Image.Height;
        double controlAspectRatio = ActualWidth / ActualHeight;

        if (imageAspectRatio > controlAspectRatio)
        {
            _scale = ActualWidth / Image.Width;
            _offset = new Point(0, (ActualHeight - Image.Height * _scale) / 2);
        }
        else
        {
            _scale = ActualHeight / Image.Height;
            _offset = new Point((ActualWidth - Image.Width * _scale) / 2, 0);
        }

        DrawImage();
        DrawROIs();
        DrawTextAnnotations();
    }

    private void DrawImage()
    {
        using (var dc = _imageVisual.RenderOpen())
        {
            if (Image != null)
            {
                ApplyTransform(dc);
                dc.DrawImage(Image, new Rect(0, 0, Image.Width, Image.Height));
                dc.Pop();
            }

            if (ShowCrosshair)
            {
                DrawCrosshair(dc);
            }
        }
    }

    private void DrawCrosshair(DrawingContext dc)
    {
        if (Image == null) return;

        double centerX = (Image.Width * _scale) / 2 + _offset.X;
        double centerY = (Image.Height * _scale) / 2 + _offset.Y;

        dc.DrawLine(new Pen(Brushes.Yellow, 1), new Point(centerX, 0), new Point(centerX, ActualHeight));
        dc.DrawLine(new Pen(Brushes.Yellow, 1), new Point(0, centerY), new Point(ActualWidth, centerY));
    }

    private void DrawROIs()
    {
        using (var dc = _roiVisual.RenderOpen())
        {
            foreach (var roi in ROIs)
            {
                roi.Draw(dc, _scale, _offset);
            }
        }

        DrawControlPoints();
    }

    private void DrawControlPoints()
    {
        using (var dc = _controlPointsVisual.RenderOpen())
        {
            if (!IsROIFunctionalityEnabled || !_isEditing) return;

            foreach (var roi in ROIs)
            {
                ApplyTransform(dc);

                var controlPoints = roi.GetControlPoints();
                foreach (var point in controlPoints)
                {
                    dc.DrawRectangle(new SolidColorBrush(Color.FromArgb(128, 255, 0, 0)), new Pen(Brushes.Red, 1), new Rect(point.X - 5 / _scale, point.Y - 5 / _scale, 10 / _scale, 10 / _scale));
                }

                dc.Pop();
            }
        }
    }

    private void DrawTextAnnotations()
    {
        using (var dc = _textAnnotationsVisual.RenderOpen())
        {
            foreach (var annotation in TextAnnotations)
            {
                var formattedText = new FormattedText(
                    annotation.Text,
                    System.Globalization.CultureInfo.CurrentCulture,
                    FlowDirection.LeftToRight,
                    new Typeface("Arial"),
                    annotation.FontSize,
                    new SolidColorBrush(annotation.Color),
                    VisualTreeHelper.GetDpi(this).PixelsPerDip);

                dc.DrawText(formattedText, new Point(annotation.Position.X * _scale + _offset.X, annotation.Position.Y * _scale + _offset.Y));
            }
        }
    }

    private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        if (!IsROIFunctionalityEnabled) return;

        var mousePosition = e.GetPosition(this);
        var imageMousePosition = TransformToImageCoordinates(mousePosition);

        if (_isEditing)
        {
            foreach (var roi in ROIs)
            {
                var controlPoints = roi.GetControlPoints();
                foreach (var point in controlPoints)
                {
                    if (IsPointNear(imageMousePosition, point))
                    {
                        _draggingControlPoint = point;
                        _currentRoi = roi;
                        return;
                    }
                }

                if (roi.Bounds.Contains(imageMousePosition))
                {
                    _isMoving = true;
                    _lastMousePosition = imageMousePosition;
                    _currentRoi = roi;
                    return;
                }
            }
        }

        _currentRoi = CreateROI(_currentRoiType);
        InitializeROI(_currentRoi, imageMousePosition);
        ROIs.Add(_currentRoi);
        _isDrawing = true;
    }

    private void OnMouseMove(object sender, MouseEventArgs e)
    {
        var mousePosition = e.GetPosition(this);
        var imageMousePosition = TransformToImageCoordinates(mousePosition);
        UpdateInfoTextBlock(imageMousePosition);

        if (!IsROIFunctionalityEnabled) return;

        if (_isDrawing)
        {
            UpdateDrawingROI(imageMousePosition);
            DrawROIs();
        }
        else if (_draggingControlPoint.HasValue)
        {
            _currentRoi.UpdateControlPoint(_draggingControlPoint.Value, imageMousePosition);
            _draggingControlPoint = imageMousePosition;
            DrawROIs();
        }
        else if (_isMoving)
        {
            var offset = new Point(imageMousePosition.X - _lastMousePosition.X, imageMousePosition.Y - _lastMousePosition.Y);
            _currentRoi.Move(offset);
            _lastMousePosition = imageMousePosition;
            DrawROIs();
        }
    }

    private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        _isDrawing = false;
        _isMoving = false;
        _draggingControlPoint = null;
    }

    private void OnMouseWheel(object sender, MouseWheelEventArgs e)
    {
        var mousePosition = e.GetPosition(this);
        var previousScale = _scale;

        _scale = e.Delta > 0 ? _scale + ScaleStep : Math.Max(_scale - ScaleStep, ScaleStep);

        var scaleRatio = _scale / previousScale;
        _offset.X = mousePosition.X - scaleRatio * (mousePosition.X - _offset.X);
        _offset.Y = mousePosition.Y - scaleRatio * (mousePosition.Y - _offset.Y);

        UpdateInfoTextBlock(mousePosition);
        DrawImage();
        DrawROIs();
        DrawTextAnnotations();
    }

    private void OnMouseRightButtonDown(object sender, MouseButtonEventArgs e)
    {
        ContextMenu.IsOpen = true;
    }

    private void CreateContextMenu()
    {
        var contextMenu = new ContextMenu();

        var rectangleMenuItem = new MenuItem { Header = "Rectangle" };
        rectangleMenuItem.Click += (s, e) => _currentRoiType = "Rectangle";
        contextMenu.Items.Add(rectangleMenuItem);

        var ellipseMenuItem = new MenuItem { Header = "Ellipse" };
        ellipseMenuItem.Click += (s, e) => _currentRoiType = "Ellipse";
        contextMenu.Items.Add(ellipseMenuItem);

        var lineMenuItem = new MenuItem { Header = "Line" };
        lineMenuItem.Click += (s, e) => _currentRoiType = "Line";
        contextMenu.Items.Add(lineMenuItem);

        var fitToWindowMenuItem = new MenuItem { Header = "Fit to Window" };
        fitToWindowMenuItem.Click += (s, e) => FitToWindow();
        contextMenu.Items.Add(fitToWindowMenuItem);

        var editMenuItem = new MenuItem { Header = "Edit ROI" };
        editMenuItem.Click += (s, e) => StartEditing();
        contextMenu.Items.Add(editMenuItem);

        var deleteMenuItem = new MenuItem { Header = "Delete ROI" };
        deleteMenuItem.Click += (s, e) => DeleteROI();
        contextMenu.Items.Add(deleteMenuItem);

        ContextMenu = contextMenu;
        UpdateContextMenu();
    }

    private void UpdateContextMenu()
    {
        foreach (MenuItem item in ContextMenu.Items)
        {
            if (item.Header.ToString() == "Rectangle" || item.Header.ToString() == "Ellipse" || item.Header.ToString() == "Line" || item.Header.ToString() == "Edit ROI" || item.Header.ToString() == "Delete ROI")
            {
                item.IsEnabled = IsROIFunctionalityEnabled;
            }
        }
    }

    private void StartEditing()
    {
        _isEditing = true;
        _isDrawing = false;
    }

    private void DeleteROI()
    {
        if (_currentRoi != null)
        {
            ROIs.Remove(_currentRoi);
            _currentRoi = null;
            DrawROIs();
        }
    }

    protected override int VisualChildrenCount => _visuals.Count + 1;

    protected override Visual GetVisualChild(int index)
    {
        if (index < 0 || index >= _visuals.Count + 1)
        {
            throw new ArgumentOutOfRangeException();
        }
        return index < _visuals.Count ? _visuals[index] : _infoTextBlock;
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        return new Size(Image?.Width ?? 0, Image?.Height ?? 0);
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        return finalSize;
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);
        drawingContext.DrawRectangle(Brushes.Black, null, new Rect(0, 0, ActualWidth, ActualHeight));
    }

    private void ApplyTransform(DrawingContext dc)
    {
        var transformGroup = new TransformGroup();
        transformGroup.Children.Add(new ScaleTransform(_scale, _scale));
        transformGroup.Children.Add(new TranslateTransform(_offset.X, _offset.Y));
        dc.PushTransform(transformGroup);
    }

    private Point TransformToImageCoordinates(Point point)
    {
        return new Point((point.X - _offset.X) / _scale, (point.Y - _offset.Y) / _scale);
    }

    private bool IsPointNear(Point p1, Point p2, double tolerance = 5)
    {
        return Math.Abs(p1.X - p2.X) < tolerance / _scale && Math.Abs(p1.Y - p2.Y) < tolerance / _scale;
    }

    private void UpdateInfoTextBlock(Point imageMousePosition)
    {
        _infoTextBlock.Text = $"[Scale: {_scale:F2}, X: {imageMousePosition.X}, Y: {imageMousePosition.Y}]";
        _infoTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
        _infoTextBlock.Arrange(new Rect(new Point(this.ActualWidth - _infoTextBlock.DesiredSize.Width, 0), _infoTextBlock.DesiredSize));
    }

    private void InitializeROI(ROI roi, Point position)
    {
        if (roi is LineROI lineRoi)
        {
            lineRoi.StartPoint = position;
            lineRoi.EndPoint = position;
        }
        else if (roi is CircleROI circleRoi)
        {
            circleRoi.Bounds = new Rect(position, new Size(0, 0));
        }
        else
        {
            roi.Bounds = new Rect(position, new Size(0, 0));
        }
    }

    private void UpdateDrawingROI(Point position)
    {
        if (_currentRoi is LineROI lineRoi)
        {
            lineRoi.EndPoint = position;
        }
        else if (_currentRoi is CircleROI circleRoi)
        {
            var centerX = circleRoi.Center.X;
            var centerY = circleRoi.Center.Y;
            var newRadius = Math.Sqrt(Math.Pow(position.X - centerX, 2) + Math.Pow(position.Y - centerY, 2));
            circleRoi.Bounds = new Rect(new Point(centerX - newRadius, centerY - newRadius), new Size(newRadius * 2, newRadius * 2));
        }
        else
        {
            var size = Math.Max(Math.Abs(position.X - _currentRoi.Bounds.Left), Math.Abs(position.Y - _currentRoi.Bounds.Top));
            _currentRoi.Bounds = new Rect(_currentRoi.Bounds.TopLeft, new Size(size, size));
        }
    }

    private ROI CreateROI(string roiType)
    {
        return roiType switch
        {
            "Rectangle" => new RectangleROI(),
            "Ellipse" => new CircleROI(),
            "Line" => new LineROI(),
            _ => new RectangleROI(),
        };
    }
}
